Codes
Code your ideas for understanding of natural systems
Updated at 2020.1.3
Updated at 2020.11.01
Updated at 2020.09.26
Intro
이 문서는 다음 포스트를 보고 작성한 것이다. Basic CRUD operations in Blazor using SQLite as the database
Blazor Server Application
에서 SQLite를 활용하여 CRUD (Create, Read, Update and Delete operations)를 구현해보는 포스트로 매우 유용하며, 아래의 순서대로 진행해 보면 된다.
SQLite
는 MySQL나 PostgreSQL와 같은 데이터베이스 관리 시스템이지만, 서버가 아니라 응용 프로그램에 넣어 사용하는 비교적 가벼운 데이터베이스이다. 영어권에서는 '에스큐엘라이트'또는 '시퀄라이트'라고 읽는다. [위키백과]
Start New Project
Visual Studio 2019
에서 Blazor Server Application
을 선택하여 새로운 프로젝트를 시작한다.
Adding NuGet Packages
다음 2개의 라이브러리를 누겟 패키지에서 검색하여 설치한다.
- Microsoft.EntityFrameworkCore.Sqlite
- Microsoft.EntityFrameworkCore.Tools
Adding Class for Data Processing
프로젝트 내 Data
디렉토리를 만들고 Data 처리를 위한 다음 3개의 Class를 추가한다.
- Product.cs: 원하는 형태의 데이터에 따라 구성을 변경하면 된다.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
}
- ProductDbContext.cs:
DbContext
클래스를 상속 받아 커스터마이즈화 한다.- Constructor
DbSet
프로퍼티OnModelCreating
메서드- 간단한 테스트를 위한
GetProducts
메서드
public class ProductDbContext : DbContext
{
#region Constructor
public ProductDbContext(DbContextOptions<ProductDbContext> options) : base(options)
{
}
#endregion
#region Public properties
public DbSet<Product> Product { get; set; }
#endregion
#region Overidden methods
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasData(GetProducts());
base.OnModelCreating(modelBuilder);
}
#endregion
#region Private methods
private List<Product> GetProducts()
{
return new List<Product>
{
new Product { Id = 1001, Name = "Laptop", Price = 20.02, Quantity = 10, Description ="This is a best gaming laptop"},
new Product { Id = 1002, Name = "Microsoft Office", Price = 20.99, Quantity = 50, Description ="This is a Office Application"},
new Product { Id = 1003, Name = "Lazer Mouse", Price = 12.02, Quantity = 20, Description ="The mouse that works on all surface"},
new Product { Id = 1004, Name = "USB Storage", Price = 5.00, Quantity = 20, Description ="To store 256GB of data"}
};
}
#endregion
}
- ProductServices.cs: DB에 연결된
DbContext
를 가지고 CRUD 작업을 하는 실질적인 클래스로서 나중에Startup.cs
에 서비스로 등록된다. DB Access시에 지연이 발생할 수 있기 때문에 대부분의 메서드가 비동기화로 선언 및 구현된다.- Constructor:
DbContext
초기화 - GetProductAsync
- AddProductAsync
- UpdateProductAsync
- DeleteProductAsync
- Constructor:
public class ProductServices
{
#region Private members
private ProductDbContext dbContext;
#endregion
#region Constructor
public ProductServices(ProductDbContext dbContext)
{
this.dbContext = dbContext;
}
#endregion
#region Public methods
/// <summary>
/// This method returns the list of product
/// </summary>
/// <returns></returns>
public async Task<List<Product>> GetProductAsync()
{
return await dbContext.Product.ToListAsync();
}
/// <summary>
/// This method add a new product to the DbContext and saves it
/// </summary>
/// <param name="product"></param>
/// <returns></returns>
public async Task<Product> AddProductAsync(Product product)
{
try
{
dbContext.Product.Add(product);
await dbContext.SaveChangesAsync();
}
catch (Exception)
{
throw;
}
return product;
}
/// <summary>
/// This method update and existing product and saves the changes
/// </summary>
/// <param name="product"></param>
/// <returns></returns>
public async Task<Product> UpdateProductAsync(Product product)
{
try
{
var productExist = dbContext.Product.FirstOrDefault(p => p.Id == product.Id);
if (productExist != null)
{
dbContext.Update(product);
await dbContext.SaveChangesAsync();
}
}
catch (Exception)
{
throw;
}
return product;
}
/// <summary>
/// This method removes and existing product from the DbContext and saves it
/// </summary>
/// <param name="product"></param>
/// <returns></returns>
public async Task DeleteProductAsync(Product product)
{
try
{
dbContext.Product.Remove(product);
await dbContext.SaveChangesAsync();
}
catch (Exception)
{
throw;
}
}
#endregion
}
Modifying import razor file
_imports.razor: 프로젝트 내 모든 razor파일에서 Data 폴더의 Data 관련 Class를 바로 사용하기 위해 다음과 같은 using
문장 추가한다.
@using [Your_Namespace].Data
Modifying Startup cs file
Startup.cs: ProductDbContext
와 ProductServices
를 등록한다. 여기서는 DB파일 이름은 Products.db
으로 하였다. ConfigureServices
관련한 부분은 나중에 추가적인 공부가 필요하다.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddDbContext<ProductDbContext>(options =>
{
options.UseSqlite("Data Source = Products.db");
});
services.AddScoped<ProductServices>();
}
Modifying Web Page
DB 사용을 위한 모든 기능 구현이 완료되었다. 이제 Web 사용자가 실제로 보는 User Interface (UI)을 바꿔야 하는데, 여기서는 Index.razor
파일에 직접 DB를 테이블 형태로 보여주고, 신규 추가 및 수정하는 UI를 꾸민다. 관련한 문법 및 상세한 사용법은 차차 익히도록 하자.
@page "/"
@inject ProductServices service
<div class="container">
<div class="row bg-light">
<table class="table table-bordered">
<thead class="thead-dark">
<tr>
<th>Product Id</th>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
<th>Description</th>
<th>Delete Product</th>
</tr>
</thead>
<tbody>
@if (Products.Any())
{
@foreach (var product in Products)
{
<tr @onclick="(() => SetProductForUpdate(product))">
<td>@product.Id</td>
<td>@product.Name</td>
<td>@product.Price</td>
<td>@product.Quantity</td>
<td>@product.Description</td>
<td><button class="btn btn-danger" @onclick="(() => DeleteProduct(product))">Delete</button></td>
</tr>
}
}
else
{
<tr><td colspan="6"><strong>No products available</strong></td></tr>
}
</tbody>
</table>
</div>
<div class="row m-5">
<div class="col-5 bg-light m-2 justify-content-start">
<div class="p-3 mb-3 bg-primary text-white text-center">Add New Product</div>
<EditForm Model="@NewProduct">
<div class="form-group">
<label for="name">Product Name</label>
<input type="text" id="name" class="form-control" @bind-value="@NewProduct.Name" />
</div>
<div class="form-group">
<label for="price">Price</label>
<input type="text" id="price" class="form-control" @bind="@NewProduct.Price" />
</div>
<div class="form-group">
<label for="quantity">Quantity</label>
<input type="text" id="quantity" class="form-control" @bind="@NewProduct.Quantity" />
</div>
<div class="form-group">
<label for="Description">Description</label>
<input type="text" id="Description" class="form-control" @bind="@NewProduct.Description" />
</div>
<div class="text-center p-3 mb-3">
<button class="btn btn-info" @onclick="AddNewProduct"> Add Product</button>
</div>
</EditForm>
</div>
<div class="col-5 bg-light m-2 justify-content-end">
<div class="p-3 mb-1 bg-primary text-white text-center">Update Product</div>
<EditForm Model="@UpdateProduct">
<div class="form-group">
<label for="name">Product Name</label>
<input type="text" id="name" class="form-control" @bind-value="@UpdateProduct.Name" />
</div>
<div class="form-group">
<label for="price">Price</label>
<input type="text" id="price" class="form-control" @bind="@UpdateProduct.Price" />
</div>
<div class="form-group">
<label for="quantity">Quantity</label>
<input type="text" id="quantity" class="form-control" @bind="@UpdateProduct.Quantity" />
</div>
<div class="form-group">
<label for="Description">Description</label>
<input type="text" id="Description" class="form-control" @bind="@UpdateProduct.Description" />
</div>
<div class="text-center p-3 mb-3">
<button class="btn btn-info" @onclick="UpdateProductData"> Update Product</button>
</div>
</EditForm>
</div>
</div>
</div>
@code {
List<Product> Products = new List<Product>();
protected override async Task OnInitializedAsync()
{
await RefreshProducts();
}
private async Task RefreshProducts()
{
Products = await service.GetProductAsync();
}
public Product NewProduct { get; set; } = new Product();
private async Task AddNewProduct()
{
await service.AddProductAsync(NewProduct);
NewProduct = new Product();
await RefreshProducts();
}
Product UpdateProduct = new Product();
private void SetProductForUpdate(Product product)
{
UpdateProduct = product;
}
private async Task UpdateProductData()
{
await service.UpdateProductAsync(UpdateProduct);
await RefreshProducts();
}
private async Task DeleteProduct(Product product)
{
await service.DeleteProductAsync(product);
await RefreshProducts();
}
}
Modifying site css file
site.css: 마우스가 위로 움직일 때 웹페이지의 Table의 Background 색깔을 변경할 수 있다. 꼭 필요한 것은 아니나, CSS에 대해 공부하여 추후 더 다양하 기능을 넣어보자.
tr:hover {
background-color:lightgray;
}
Migration and Update DB
실행하기 전에 DB를 초기화하여야 한다. 하기 명령을 통하 ProductDbContext
클래스의 GetProduct
메서드에서 정의된 데이터가 자동으로 업데이트 되게 할 수 있다. Package Manager Console
을 열어서 Default Project
를 해당 프로젝트로 선택하고, 다음 두 개의 명령어를 입력한다.
- Add-Migration "Initial-Commit"
- Update-Database
위와 같이하면 Migrations
폴더가 생기고 그 아래에 관련 파일(.cs)들이 생긴다. SQLiteStudio
를 다운 받아서 Products.db
를 열면 Database가 제대로 생성되었는지 확인해 볼 수 있다.
Trying Run
Ctrl+F5
를 눌러서 Web를 실행시켜 보면 제대로 되는 것을 확인할 수 있다.
Publishing to Azure
이 프로젝트를 Azure로 Publish하기만 하면 끝인데, 현재까지는 다음과 같은 에러가 발생한다. 아직 해결책을 못찾았다. 아마 Azure에서는 SQLite를 막아놓은 것 같다.
Error.
An error occurred while processing your request.
Development Mode
Swapping to Development environment will display more detailed information about the error that occurred.
The Development environment shouldn't be enabled for deployed applications. It can result in displaying sensitive information from exceptions to end users. For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.
총 17 개의 글이 있습니다.
# | 제목 | 날짜 | 조회수 |
---|---|---|---|
01 | CS 배우기 요약 | 2021/06/07 | 144 |
02 | CS Statements | 2021/06/07 | 128 |
03 | 퍼셉트론 | 2021/04/15 | 124 |
04 | Blazor and Sqlite | 2021/04/15 | 137 |
05 | Blazor Layouts | 2021/04/15 | 160 |
06 | CS Language Reference | 2021/06/07 | 126 |
07 | VSCode and Markdown | 2021/04/15 | 137 |
08 | Blazor에서 이미지파일 다루기 | 2021/06/10 | 211 |
09 | Blazor and Markdown | 2021/04/15 | 144 |
10 | 종속성 주입 | 2021/06/07 | 152 |
11 | Blazor에서 데이터 다루기 | 2021/06/07 | 137 |
12 | Blazor Components | 2021/04/15 | 147 |
13 | CS Glossary | 2021/06/07 | 125 |
14 | Enum 타입 다루기 | 2021/12/14 | 134 |
15 | 생활코딩 CS01 | 2022/04/25 | 261 |
16 | 생활코딩 CS02 | 2022/04/30 | 165 |
17 | 생활코딩 CS03 | 2022/04/30 | 441 |